# Python 面向对象

# 知识点:
# 5.1 面向对象
# 5.3 python对象销毁(垃圾回收)

# 5.8枚举

# 创建类
# 语法:
# class ClassName:
#    '类的帮助信息'   #类文档字符串
#    class_suite  #类体
from class_test.Jim import Jim
from class_test.bench import Bench
from class_test.bench_for_json import BenchForJson
from class_test.student import Student
from types import MethodType
from enum import Enum, unique
import logging
import pickle
import json

from class_test.weekday import Weekday

print("----------面向对象---------------")
logging.basicConfig(level=logging.INFO)


class Student2(object):
    """ this is description of class """
    pass


# 语法格式如下：
#
# class ClassName:
#     <statement-1>
#     .
#     .
#     .
#     <statement-N>

# 5.1
stu = Student("Bench", 26, 99)
print("==stu = ", stu)
print("==school_name= ", Student.school_name)
stu.print_name_age_score()
stu.print_score()

# hasattr(stu, 'age')    # 如果存在 'age' 属性返回 True。
# getattr(stu, 'age')    # 返回 'age' 属性的值
# setattr(stu, 'age', 8) # 添加属性 'age' 值为 8
# delattr(stu, 'age')    # 删除属性 'age'
print("if has age ", hasattr(stu, "age"))
print(" set age ", setattr(stu, "age", 28))
print(" get age ", getattr(stu, "age"))

# stu2 = Student("tom", 26, -8)
# print("==stu2 = ", stu2)
# stu2.print_name_age_score()
# stu2.print_score()

# 5.2
# Python内置类属性
# __dict__ : 类的属性（包含一个字典，由类的数据属性组成）
# __doc__ :类的文档字符串
# __name__: 类名
# __module__: 类定义所在的模块（类的全名是'__main__.className'，如果类位于一个导入模块mymod中，那么className.__module__ 等于 mymod）
# __bases__ : 类的所有父类构成元素（包含了一个由所有父类组成的元组）

stu3 = Student("Bob", 16, 88)
print("Student.__dict__ ", Student.__dict__)
print("Student.__doc__ ", Student.__doc__)
print("Student.__name__ ", Student.__name__)
print("Student.__module__ ", Student.__module__)
print("Student.__bases__ ", Student.__bases__)

# 5.3
# python对象销毁(垃圾回收)
# Python 使用了引用计数这一简单技术来跟踪和回收垃圾。
# 在 Python 内部记录着所有使用中的对象各有多少引用
# 当对象被创建时， 就创建了一个引用计数， 当这个对象不再需要时， 也就是说， 这个对象的引用计数变为0 时， 它被垃圾回收。
# 但是回收不是"立即"的， 由解释器在适当的时机，将垃圾对象占用的内存空间回收
# 垃圾回收机制不仅针对引用计数为0的对象，同样也可以处理循环引用的情况。循环引用指的是，两个对象相互引用，但是没有其他变量引用他们。
# 这种情况下，仅使用引用计数是不够的。Python 的垃圾收集器实际上是一个引用计数器和一个循环垃圾收集器。作为引用计数的补充，

# 5.4 类的继承
# 继承语法
# class 派生类名(基类名)
# ...

stu4 = Student("Jim", 46, 98)
stu4.print_class_info()

ben = Bench("bench_enc")
print(ben.get_name())
ben.print_class_info()


# python 支持多继承
# class A:        # 定义类 A
# .....
#
# class B:         # 定义类 B
# .....
#
# class C(A, B):   # 继承类 A 和 B
# .....


# 5.5类属性与方法
# 类的私有属性
# __private_attrs：两个下划线开头，声明该属性为私有，不能在类的外部被使用或直接访问。在类内部的方法中使用时 self.__private_attrs。
#
# 类的方法
# 在类的内部，使用 def 关键字可以为类定义一个方法，与一般函数定义不同，类方法必须包含参数 self,且为第一个参数
# 单下划线、双下划线、头尾双下划线说明：
# __foo__: 定义的是特殊方法，一般是系统定义名字 ，类似 __init__() 之类的。
#
# _foo: 以单下划线开头的表示的是 protected 类型的变量，即保护类型只能允许其本身与子类进行访问，不能用于 from module import *
#
# __foo: 双下划线的表示的是私有类型(private)的变量, 只能是允许这个类本身进行访问了


# 继承和多态

# 5.6 获取对象信息
# 判断对象类型，使用type()函数：
# 判断class的类型，可以使用isinstance()函数
# 获得一个对象的所有属性和方法，可以使用dir()函数


# 5.6 使用__slots__
# 创建了一个class的实例后，我们可以给该实例绑定任何属性和方法，这就是动态语言的灵活性
def set_height(self, height):
    self.height = height


def set_zone(self, zone):
    self.zone = zone


ben2 = Bench("bench2")
# 给实例绑定一个体重的属性
ben2.weight = "75kg"
print("ben weight =", ben2.weight)
# 实例绑定一个方法
ben2.set_height = MethodType(set_height, ben2)
ben2.set_height(179)
print("height = ", ben2.height)

# 但是，给一个实例绑定的方法，对另一个实例是不起作用的：

# AttributeError: 'Bench' object has no attribute 'height'
# ben3 = Bench("bench3")
# ben3.set_height(169)
# print("height = ", ben3.height)

# 为了给所有实例都绑定方法，可以给class绑定方法：
Bench.set_zone = set_zone

ben4 = Bench("bench4")
ben4.set_zone("北京")
print("zone = ", ben4.zone)

# 如果我们想要限制实例的属性怎么办？比如，只允许对Student实例添加name和age属性


# 5.7 使用@property
# Python内置的@property装饰器可以把类的方法伪装成属性调用的方式。也就是本来是Foo.func()的调用方法，变成Foo.func的方式


jim = Jim("jack", 18)
print(jim.age)
jim.age = 19
print("jim.age:  ", jim.age)
del jim.age

# 将一个方法伪装成为属性后，就不再使用圆括号的调用方式了。而是类似变量的赋值、获取和删除方法了。当然，每个动作内部的代码细节还是需要你自己根据需求去实现的
# 那么如何将一个普通的方法转换为一个“伪装”的属性呢？
# 1.首先，在普通方法的基础上添加@property装饰器，例如上面的age()方法。这相当于一个get方法，用于获取值,决定类似"result = obj.age"执行什么代码。
# 该方法仅有一个self参数。
# 2.写一个同名的方法，添加@xxx.setter装饰器（xxx表示和上面方法一样的名字），比如例子中的第二个方法。
# 这相当于编写了一个set方法，提供赋值功能，决定类似"obj.age = ...."的语句执行什么代码。
# 3.再写一个同名的方法，并添加@xxx.delete装饰器，比如例子中的第三个方法。用于删除功能，决定"del obj.age "这样的语句具体执行什么代码

# 5.7.1 property()函数
# 除了使用装饰器的方式将一个方法伪装成属性外，Python内置的builtins模块中的property()函数，为我们提供了第二种设置类属性的手段。
ji = Jim("tom", 18)
print(ji.name)
ji.name = "Jim"
print("jim.name:  ", ji.name)
del jim.name

# 5.7.2多重继承
# 在设计类的继承关系时，通常，主线都是单一继承下来的
# 如果需要“混入”额外的功能，通过多重继承就可以实现，比如，让duck除了继承自Bird外，再同时继承Runnable。这种设计通常称之为MixIn。(类似接口,表示具备什么功能)
# MixIn的目的就是给一个类增加多个功能，这样，在设计类的时候，我们优先考虑通过多重继承来组合多个MixIn的功能，而不是设计多层次的复杂的继承关系。

# 5.7.3 定制类
#  定制__repr__,或者定制__str__

jiy = Jim("tom", 18)
# 如果没有定制方法__repr__,或者定制方法__str__ 打印输出 <class_test.Jim.Jim object at 0x10a3ab850>
# 定制方法
print(jiy)
print(Jim("ben", "23"))
logging.info("this log")
logging.info(jiy)

# TODO
# __iter__
# __call__
# __getitem__
# __getattr__

# 5.8 使用枚举类
# Python提供了Enum类
Month = Enum('Month', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'))

for name, member in Month.__members__.items():
    print(name, '=>', member, ',', member.value)

    # 如果需要更精确地控制枚举类型，可以从Enum派生出自定义类：

    # @unique
    # class Weekday(Enum):
    #     Sun = 0  # Sun的value被设定为0
    #     Mon = 1
    #     Tue = 2
    #     Wed = 3
    #     Thu = 4
    #     Fri = 5
    #     Sat = 6

    day1 = Weekday.Sun
    print(day1)
    print(Weekday(1))

# 5.9 序列化
print("---------序列化----------")
# 我们把变量从内存中变成可存储或传输的过程称之为序列化，在Python中叫pickling，在其他语言中也被称之为serialization，marshalling，flattening等等，都是一个意思。
# 序列化之后，就可以把序列化后的内容写入磁盘，或者通过网络传输到别的机器上。
#
# 反过来，把变量内容从序列化的对象重新读到内存里称之为反序列化，即unpickling。
#
# Python提供了pickle模块来实现序列化。

d = dict(name='Bob', age=20, score=88)

print(pickle.dumps(d))
# pickle.dumps()方法把任意对象序列化成一个bytes，然后，就可以把这个bytes写入文件。或者用另一个方法pickle.dump()直接把对象序列化后写入一个file-like Object：
#
with open("/PythonSpace/python_test2.txt", "wb") as filer:
    pickle.dump(d, filer)

with open("/PythonSpace/python_test2.txt", "rb") as filer2:
    d2 = pickle.load(filer2)
    print("d2====", d2)

#     JSON
# Python内置的json模块提供了非常完善的Python对象到JSON格式的转换。我们先看看如何把Python对象变成一个JSON：
d = dict(name='Bob', age=20, score=88)
json.dumps(d)
print("after json====", json.dumps(d))
# 要把JSON反序列化为Python对象，用loads()或者对应的load()方法，前者把JSON的字符串反序列化，后者从file-like Object中读取字符串并反序列化：
json_str = '{"age": 20, "score": 88, "name": "Bob"}'
json.loads(json_str)
# class 类序列化为 json

s = BenchForJson('Bob', 20, 88)


# 或者定义在BenchForJson文件的同一个级别
# 实例首先被bench2dict()函数转换成dict，然后再被顺利序列化为JSON：
def bench2dict(self):
    return {
        'name': self.name,
        'age': self.age,
        'score': self.score
    }


print("class2json", json.dumps(s, default=bench2dict))
# print("class2json===", json.dumps(s, default=lambda obj: obj.__dict__))
